package ru.ifmo.math.matrix;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static ru.ifmo.math.matrix.MatrixUtil.*;

/**
 * @author avhaliullin
 */
public class CodingUtil {
    public static BinaryMatrix checkoutMatrix(BinaryMatrix matrix) {
        int n = matrix.getWidth();
        int k = matrix.getHeight();
        int r = n - k;
        List<Transformation> transformations = new ArrayList<Transformation>();
        BinaryMatrix systematized = systematize(matrix, transformations);
        BinaryMatrix p = subMatrix(systematized, 0, k, k, r);
        return new ArrayMatrix(transformations.get(0).transform(concatenateHorizontal(transponate(p), identityMatrix(r))));
    }

    public static BinaryMatrix originalMatrix(BinaryMatrix matrix) {
        int n = matrix.getWidth();
        int k = matrix.getHeight();
        int r = n - k;
        List<Transformation> transformations = new ArrayList<Transformation>();
        BinaryMatrix systematized = systematize2(matrix, transformations);
        BinaryMatrix p = subMatrix(systematized, 0, k, 0, r);
        return new ArrayMatrix(transformations.get(0).transform(concatenateHorizontal(identityMatrix(r), transponate(p))));
    }

    public static List<byte[]> allCodes(BinaryMatrix matrix) {
        List<boolean[]> templates = new ArrayList<boolean[]>();
        boolean[] init = new boolean[matrix.getHeight()];
        init[0] = true;
        templates.add(init);
        while (true) {
            boolean[] next = templates.get(templates.size() - 1).clone();
            int j = 0;
            for (int i = 0; i < next.length && next[i]; i++) {
                next[i] = false;
                j = i + 1;
            }
            if (j == next.length) {
                break;
            }
            next[j] = true;
            templates.add(next);
        }

        List<byte[]> res = new ArrayList<byte[]>();

        for (boolean[] template : templates) {
            byte[] cur = new byte[matrix.getWidth()];
            for (int i = 0; i < template.length; i++) {
                if (template[i]) {
                    for (int j = 0; j < matrix.getWidth(); j++) {
                        cur[j] = (byte) ((cur[j] + matrix.get(i, j)) % 2);
                    }
                }
            }
            res.add(cur);
        }
        return res;

    }

    public static int weight(byte[] code) {
        int size = 0;
        for (byte aCur : code) {
            size += aCur;
        }
        return size;
    }

    public static Map<Integer, Integer> spectrum(List<byte[]> codes) {
        Map<Integer, Integer> res = new HashMap<Integer, Integer>();
        for (byte[] code : codes) {
            int weight = weight(code);
            Integer s = res.get(weight);
            if (s == null) {
                s = 0;
            }
            res.put(weight, s + 1);
        }
        return res;
    }

    public static byte[] minWeightWord(List<byte[]> codes) {
        byte[] minWord = null;
        int minSize = codes.get(0).length;

        for (byte[] cur : codes) {
            int size = weight(cur);
            if (size <= minSize) {
                minSize = size;
                minWord = cur;
            }
        }
        return minWord;
    }

    public static Map<List<Byte>, byte[]> syndroms(List<byte[]> errors, BinaryMatrix h) {
        h = transponate(h);
        Map<List<Byte>, byte[]> res = new HashMap<List<Byte>, byte[]>();
        for (byte[] error : errors) {
            byte[] syndrom = multiply(error, h);
            List<Byte> syndrom2 = new ArrayList<Byte>();
            for (byte b : syndrom) {
                syndrom2.add(b);
            }
            byte[] oldError = res.get(syndrom2);
            if (oldError == null || weight(oldError) > weight(error)) {
                res.put(syndrom2, error);
            }
        }
        return res;
    }

    public static List<byte[]> errors(List<byte[]> codes) {
        byte[] init = new byte[codes.get(0).length];
        init[0] = 1;
        List<byte[]> res = new ArrayList<byte[]>();
        res.add(init);
        while (true) {
            byte[] next = res.get(res.size() - 1).clone();
            boolean end = true;
            for (int i = 0; i < next.length; i++) {
                if (next[i] == 1) {
                    next[i] = 0;
                } else {
                    next[i] = 1;
                    end = false;
                    break;
                }
            }
            if (end) {
                break;
            }
            res.add(next);
        }
        res.removeAll(codes);
        return res;
    }

}

